// 大文件上传工具
import ThreadPool from './exec/ThreadPool.js';
import extentContext from '../context.js';
import { deepMerge } from './utils';

/**
 * 构造参数：
 * @param {Object} config 配置
 * @param {Number} config.chunkSize 分片大小
 * @param {Number} config.thresholdSize 分割阈值
 * @param {Number} config.threadSize 线程大小
 * @param {String} config.baseUrl 接口URI
 * @param {Object} config.requestHeaders 请求头
 * @param {String} config.saveTo 保存位置
 * @param {Number} config.retryCount 重试次数
 * @param {Boolean} config.autoCloseThreadPool 是否自动关闭线程池
 * 
 * 
 * 

返回结果
{
    "code": 2,
    "oss": {
        "id": "8c9adf5e-ca48-4230-9775-f0c8a9b8f0ac",
        "fileName": "DSC_1453.MOV",
        "mime": "video/quicktime",
        "fileSize": 833451500,
        "count": 40,
        "saveTo": "SHARE"
    }
}

 */
export default class MaxFileUploader{
  __v_skip = true; // 设置vue跳过代理
  threadPool = null; // 线程池

  // 配置
  _config = {
    /**分片大小20M */
    chunkSize: 20971520,
    /**100M才开始分割 */
    thresholdSize: 104857600,
    /**线程大小 */
    threadSize: 4,
    baseUrl: null,
    requestHeaders: null, // 请求头
    saveTo: 'SHARE',
    retryCount: 3,
    autoCloseThreadPool: false, // 是否自动关闭线城池
    fileField: 'file', // 删除文件字段名
  };

  options = null; // 上传参数
  startDate = null; // 任务开始时间
  uploadDate = null; // 上传完成时间
  resultDate = null; // 获取结果时间
  totalSize = 0; // 文件总大小
  loaded = 0; // 完成量
  chunkCount = 0; // 分片数量
  groupId = null; // 任务ID


  status = MaxFileUploader.INIT;

  constructor(config){
    let self = this;
    try{
      let conf = self._config = deepMerge(self._config, config);

      if (!conf.baseUrl) {
        throw new Error('config.baseUrl是必须的');
      }

      if (!conf.chunkSize > conf.thresholdSize) {
        throw new Error('chunkSize 不能大于thresholdSize');
      }
      
    } catch(e){
      self._step(MaxFileUploader.FAIL, '初始化错误', e);
      throw e;
    }
  }

  init(){
    let self = this;
    if(this.status != MaxFileUploader.INIT){
      return;
    }
    let conf = this._config;


    let eContext = extentContext();

    // 初始化线程池
    if (conf.threadPool) {
      self.threadPool = conf.threadPool;
      conf.autoCloseThreadPool = false;
    } else if (eContext.maxFileUploaderThreadPool){
      if (eContext.maxFileUploaderThreadPool.state != 0) {
        self.threadPool = eContext.maxFileUploaderThreadPool;
        conf.autoCloseThreadPool = false;
      } else{
        eContext.maxFileUploaderThreadPool = null;
      }
    } else {
      self.threadPool = new ThreadPool({
        coreSize: conf.threadSize
      });
      eContext.maxFileUploaderThreadPool = self.threadPool;
      conf.autoCloseThreadPool = true;
    }

    if (!conf.md5Url) {
      conf.md5Url = conf.baseUrl + '/res/md5.js';
    }
    let md5Url = new URL(conf.md5Url, location);

    self.threadPool.init((md5Url) => {
      // eslint-disable-next-line no-undef
      importScripts(md5Url);
    }, md5Url.href);
    self._step(MaxFileUploader.READY, '已就绪');
  }

  /**
   * 开始上传
   * @param {Blob} blob 文件对象
   * @param {Object} options 上传参数
   * @param {String} options.data 附件信息
   * @param {String} options.onProgress 文件上传进度
   * @param {String} options.onStep 上传步骤
   * @param {String} options.onSmallFile 小文件时回调
   * 
   * @returns 
   */
  upload(blob, options){
    let self = this;
    return new Promise((resolve, reject) => {
      self.options = Object.assign({
        data: {
          fileName: blob.name
        }
      }, options);

      if (!self.options.data.fileName){
        self.options.data.fileName = blob.name;
      }

      if (!(blob instanceof Blob)) {
        return Promise.reject(new Error('文件必须是Blob类型'));
      }

      if (options.onSmallFile instanceof Function && blob.size <= self._config.thresholdSize){
        options.onSmallFile(resolve, reject);
        return;
      }


      if (self.status == MaxFileUploader.INIT) {
        self.init();
      } else if (self.status != MaxFileUploader.READY) {
        return Promise.reject(new Error('任务进行中'));
      }
      

      self.startDate = new Date();
      self._step(MaxFileUploader.CREATE, '任务已创建');
    
    
      try {
        self.totalSize = blob.size;
        self.chunkCount = Math.ceil(blob.size / self._config.chunkSize);

        // 请求创建大文件任务
        self._createTask(blob).then(resolve).catch(reject);

      } catch (e) {
        self._step(MaxFileUploader.FAIL, '运行时错误', e);
        throw e;
      }
    });
  }

  _createTask(blob){
    let self = this;
    return fetch(self._config.baseUrl, {
      method: 'PUT',
      headers: Object.assign({
        'Content-Type': 'application/json',
      }, self._config.requestHeaders),
      body: JSON.stringify({
        ...self.options.data,
        fileSize: blob.size,
        mime: blob.type,
        count: self.chunkCount,
        saveTo: self._config.saveTo,
      })
    }).then(r=>{
      if(r.ok && r.status == 200){
        return r.text();
      }
      return new Promise((resolve, reject)=>{
        r.text().then(res=>{
          reject(new Error(res));
        });
      });
    }).then(res => {
      self.groupId = res;
      // 开始上传
      return self._upload(blob).then(chunks => {
        return self._merge(chunks);
      });
    }).catch(err => {
      self._step(MaxFileUploader.FAIL, '创建任务错误', err);
      return Promise.reject(err);
    });
  }

  _upload(blob){
    let self = this;
    self._step(MaxFileUploader.UPLOADING, '上传中');
    self.loaded = 0;
    let promises = [];
    let chunkCount = self.chunkCount;
    let totalSize = self.totalSize;
    let chunkSize = self._config.chunkSize;
    let index = 0;

    console.log(self);

    self.threadPool.onmessage = function (result) {
      if (result.key == 'chunk_err'){
        console.error('分片上传失败', result);
      } else {
        self.loaded += result.loaded;
        let percentComplete = parseInt(self.loaded / totalSize * 10000) / 100;
        self.options.onProgress && self.options.onProgress(percentComplete, self.loaded, totalSize);
      }
    };

    while (index < chunkCount) {
      let task = self._submitUpload(blob, index++, chunkSize, totalSize);
      promises.push(task.promise);
    }

    return Promise.all(promises).then(res => {
      let array = [];
      res.forEach(item=>{
        array[item.out.index] = item.out.hash;
      });
      self.uploadDate = new Date();

      console.log('上传用时', self.uploadDate - self.startDate);
      return array;
    });

  }


  /**
   * 提交上传
   */
  _submitUpload(blob, index, chunkSize, totalSize){
    let self = this;
    if (self.status != MaxFileUploader.UPLOADING) {
      return Promise.reject(new Error('任务已取消'));
    }
    // 提交到线城池中执行
    return self.threadPool.submit(function (data) {

      function checkChunk(data) {
        // 拆分
        let start = data.index * data.chunkSize;
        const chunk = data.blob.slice(start, start + data.chunkSize);
        // 创建Reader对象
        let reader = new FileReader();

        reader.onload = function () {
          try {
            // eslint-disable-next-line no-undef
            let hash = md5(reader.result);
            chunk.hash = hash;

            // 检查分片是否已上传
            fetch(new URL(`${data.baseUrl}/check/${data.groupId}?hash=${hash}&index=${data.index}`, data.origin).href, {
              method: 'GET',
              headers: data.requestHeaders
            }).then(r => r.json()).then(res => {
              if (res.code == 2) {
                // 分片已上传
                postMessage({ key: 2, loaded: chunk.size });
                postMessage({ key: 0, args: { index: data.index, hash } });
                return res;
              } else if (res.code == 0) {
                return uploadChunk(data, hash, chunk);
              }
            }).catch(err => {
              postMessage({ key: 'chunk_err', index: data.index, hash, err });
            });
          } catch (e) {
            postMessage({ key: 'chunk_err', index: data.index, err: e });
          }

        };
        reader.readAsArrayBuffer(chunk);
      }

      function uploadChunk(data, hash, chunk) {
        return new Promise((resolve, reject) => {
          // 需要上传
          let formData = new FormData();
          formData.append(data.fileField || 'file', chunk);

          const xhr = new XMLHttpRequest();

          // 监听上传进度
          let prev = 0;
          xhr.upload.addEventListener('progress', function (event) {
            if (event.lengthComputable) {
              postMessage({ key: 2, loaded: Math.abs(prev - event.loaded) });
              prev = event.loaded;
            }
          }, false);

          // 处理请求完成的事件
          xhr.onload = function () {
            if (xhr.status === 200) {
              let res = JSON.parse(xhr.response);
              if (res.code == 0) {
                postMessage({ key: 0, args: { index: data.index, hash } });
                resolve();
                return;
              }
            }
            postMessage({ key: 1, args: { index: data.index } });
          };

          // 处理请求错误的事件
          xhr.onerror = function () {
            postMessage({ key: 1, args: { index: data.index } });
            reject();
          };

          // 发送请求
          xhr.open('POST', new URL(`${data.baseUrl}/upload/${data.groupId}?hash=${hash}`, data.origin).href, true);
          // 设置请求头
          for (let k in data.requestHeaders) {
            xhr.setRequestHeader(k, data.requestHeaders[k]);
          }
          console.log(formData);
          xhr.send(formData);
        });
      }

      try {
        checkChunk(data);
      } catch (err) {
        postMessage({ key: 'chunk_err', err, index: data.index });
      }
    }, {
      blob, chunkSize, totalSize,
      index,
      groupId: self.groupId,
      baseUrl: self._config.baseUrl,
      requestHeaders: self._config.requestHeaders,
      retryCount: self._config.retryCount,
      origin: location.origin,
      fileField: self._config.fileField,
    });
  }


  /**
   * 合并
   */
  _merge(chunks){
    let self = this;
    self._step(MaxFileUploader.MERGING, '开始合并文件');
    return fetch(new URL(`${self._config.baseUrl}/merge/${self.groupId}`, location).href, {
      method: 'POST',
      body: JSON.stringify(chunks),
      headers: {
        'Content-Type': 'application/json',
        ...self._config.requestHeaders
      }
    }).then(() => {
      return self._getResult();
    });
  }

  _getResult(){
    let self = this;
    return new Promise((resolve, reject)=>{
      function request(){
        self._requestResult().then(res => {
          if (res.code == 1) {
            // 继续请求
            setTimeout(() => {
              request();
            }, 1000);
          } else if (res.code == 2) {
            self.resultDate = new Date();
            resolve(res);
          } else if(res.code == 4){
            reject(res);
          }
        }).catch(reject);
      }
      request();
    }).then(res => {
      self._step(MaxFileUploader.COMPLETE, '完成');
      return res;
    });
  }
  _requestResult(){
    let self = this;
    return fetch(new URL(`${self._config.baseUrl}/result/${self.groupId}`, location).href, {
      headers: self._config.requestHeaders
    }).then(r => r.json());
  }


  _step(status, message, ...args){
    let self = this;
    self.status = status;
    if (status == MaxFileUploader.FAIL){
      self.options.onStep && self.options.onStep(status, message, ...args);
      self.status = MaxFileUploader.READY;
      self.reset();
    } else if (status == MaxFileUploader.COMPLETE) {
      self.options.onStep && self.options.onStep(status, message, ...args);
      self.status = MaxFileUploader.READY;
      self.reset();
    } else {
      self.options.onStep && self.options.onStep(status, message, ...args);
    }
  }

  reset(){
    let self = this;
    self.options = null; // 上传参数
    self.startDate = null; // 任务开始时间
    self.uploadDate = null; // 上传完成时间
    self.resultDate = null; // 获取结果时间
    self.totalSize = 0; // 文件总大小
    self.loaded = 0; // 完成量
    self.chunkCount = 0; // 分片数量
    self.groupId = null; // 任务ID

    self.status = MaxFileUploader.READY;
  }

  /**
   * 取消执行
   */
  cancel(){
    let self = this;
    self._progress(MaxFileUploader.CANCEL, '取消上传');
  }

  destroy(){
    let self = this;
    self.reset();
    if (self.threadPool && self._config.autoCloseThreadPool){
      self.threadPool.destroy();
    }
  }

  /**失败 */
  static FAIL = -1;
  /**初始化 */
  static INIT = 0;
  /**就绪 */
  static READY = 1;
  /**切割中 */
  static CREATE = 2;
  /**上传中 */
  static UPLOADING = 3;
  /**合并中 */
  static MERGING = 4;
  /**完成 */
  static COMPLETE = 5;
  /**重试 */
  static RETRY = 6;
  /**取消 */
  static CANCEL = 7;

}
